﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;
using AutoMapper;

namespace MVVMFramework.ViewModel
{
    /// <summary>
    /// This class provides standard functions for working with edit/view/insert an entity. 
    /// The class should be inherited
    /// </summary>
    /// <typeparam name="T">Entity object</typeparam>
    public abstract class EditEntityViewModelBase<T> : ViewModelBase where T : class
    {
        /// <summary>
        /// Gets the view model request.
        /// </summary>
        protected readonly Requests Request;
        /// <summary>
        /// Gets the original entity that was passed to the constructor.
        /// </summary>
        protected readonly T OriginalEntity;

        private readonly IDataChangedController dataChangeController = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="EditEntityViewModelBase&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="entity">The entity to be using according request.</param>
        /// <param name="request">The request of the using of the entity.</param>
        protected EditEntityViewModelBase(T entity, Requests request)
            : this(entity, request, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EditEntityViewModelBase&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="entity">The entity to be using according request.</param>
        /// <param name="request">The request of the using of the entity.</param>
        /// <param name="dataChangeController">The data change controller.</param>
        protected EditEntityViewModelBase(T entity, Requests request, IDataChangedController dataChangeController)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");

            Request = request;
            OriginalEntity = entity;

            this.entity = CreateEntityCopy(entity);

            INotifyPropertyChanged notifyPropertyChanged = this.entity as INotifyPropertyChanged;
            if (notifyPropertyChanged != null)
                notifyPropertyChanged.PropertyChanged += OnEntityPropertyChanged;

            title = GetTitle();

            this.dataChangeController = dataChangeController;

            IEnumerable<IValidator> validators = GetValidators(this.entity);
            foreach (var validator in validators)
                RegisterValidator(validator);
        }

        #region Properties

        private string title;
        /// <summary>
        /// Gets or sets the title that should be displayed on View
        /// </summary>
        public string Title
        {
            get
            {
                return title;
            }
            set
            {
                if (title != value)
                {
                    title = value;
                    OnPropertyChanged(() => Title);
                }
            }
        }

        private readonly T entity;
        /// <summary>
        /// Copy of original Entity.
        /// </summary>
        /// <remarks>
        /// View should work with this Entity object because need to save state of original entity
        /// </remarks>
        public T Entity
        {
            get
            {
                return entity;
            }
        }

        #endregion

        #region Commands

        /// <summary>
        /// Gets the save command
        /// </summary>
        public ICommand SaveCommand
        {
            get
            {
                return new RelayCommand(() =>
                {
                    if (SaveEntity())
                        RaiseViewCloseRequest(true);
                });
            }
        }


        /// <summary>
        /// Gets the cancel command
        /// </summary>
        public virtual ICommand CancelCommand
        {
            get
            {
                return new RelayCommand(() => RaiseViewCloseRequest(false));
            }
        }

        #endregion

        #region Abstract Methods

        /// <summary>
        /// Saves the specified entity
        /// </summary>
        protected abstract bool Save(T entity);
        /// <summary>
        /// Validates the specified entity
        /// </summary>
        protected abstract IEnumerable<IValidator> GetValidators(T entity);
        /// <summary>
        /// Gets the title that should be displayed on View
        /// </summary>
        protected abstract string GetTitle();

        #endregion

        #region Protected Methods

        /// <summary>
        /// Called when showing of the validation message is requested.
        /// </summary>
        protected virtual void OnValidationFailed(ValidationFailedEventArgs arg)
        {

        }

        private void validationFailed()
        {
            ValidationFailedEventArgs arg = new ValidationFailedEventArgs(this);
            OnValidationFailed(arg);
            if (!arg.Handled)
                Global.RaiseValidationFailedEvent(this, arg);
        }

        /// <summary>
        /// Creates the entity copy
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="originalEntity">The original entity to be copied</param>
        /// <returns></returns>
        protected virtual T CreateEntityCopy(T originalEntity)
        {
            Mapper.CreateMap<T, T>();
            //make backup of entity (for cancel editing)
            T copy = Mapper.Map<T, T>(originalEntity);

            return copy;
        }

        /// <summary>
        /// Flushes the fields values to the original entity.
        /// </summary>
        /// <typeparam name="T">The type of the entity.</typeparam>
        /// <param name="fromEntity">The entity copy.</param>
        /// <param name="toEntity">The original entity.</param>
        protected virtual void FlushFieldsToOriginalEntity(T fromEntity, T toEntity)
        {
            Mapper.CreateMap<T, T>();
            Mapper.Map(fromEntity, toEntity);
        }

        protected bool SaveEntity()
        {
            if (Validate())
            {
                if (Save(entity))
                {
                    FlushFieldsToOriginalEntity(entity, OriginalEntity);
                    if (dataChangeController != null)
                        dataChangeController.DataChanged();
                    return true;
                }
            }
            else
            {
                validationFailed();
                return false;
            }
            return false;
        }

        protected virtual void OnEntityPropertyChanged(object sender, PropertyChangedEventArgs e)
        {

        }

        #endregion

    }
}
